/*
	* Created by : Varun Tyagi.
	*
	* This tutorial being an advanced one, takes everything from the previous tutorials and mix them to make
	* moving dot.
*/

/* Include the headers. */
#include "SDL.h"
#include "SDL_image.h"
#include <string>

/* Screen attributes. */
const int SCREEN_HEIGHT = 640;
const int SCREEN_WIDTH = 480;
const int SCREEN_BPP = 32;

/* Frame rates. */
const int FRAMES_PER_SECOND = 20;

/* Dimensions of the dots. */
const int DOT_WIDTH = 20;
const int DOT_HEIGHT = 20;

/* The surface. */
SDL_Surface *pDot = nullptr;
SDL_Surface *pScreen = nullptr;

/* The event structure. */
SDL_Event event;

/* Class DOT. */
class Dot
{
private:

	/* X and Y offset of the dot. */
	int m_iX, m_iY;

	/* The velocity of the dot. */
	int m_iXVel, m_iYVel;

public:
	
	/* Initialize the dot. */
	Dot();

	/* Takes the key presses and adjust the dot's velocity. */
	void handle_inputs( void );

	/* Move the dot. */
	void move_dot( void );

	/* Show the dot on the screen. */
	void show_dot ( void );
};

/* Timer class. */
class Timer
{
private:

	/* Start tick of the timer. */
	int m_iStartTick;

	/* Paused tick of the timer. */
	int m_iPauseTick;

	/* Timer status. */
	bool m_bPaused;
	bool m_bStarted;

public:

	/* Initialize the timer. */
	Timer();

	/* Action functions. */
	void start( void );
	void stop( void );
	void pause( void );
	void unpause( void );

	/* Getter functions. */

	/* Get the timers time. */
	int get_ticks( void );

	/* Check the status of the timer. */
	bool has_started( void );
	bool is_paused( void );
};






/* NON CLASS FUNCTIONS. */

/* Image Loader. */
SDL_Surface* load_image( std::string filename )
{
	/* The image loader. */
	SDL_Surface *pLoadedImage = nullptr;

	/* The optimized version of the loaded image. */
	SDL_Surface *pOptimizedImage = nullptr;

	/* Load the image. */
	pLoadedImage = IMG_Load( filename.c_str() );

	/* Check the loaded image. */
	if( nullptr != pLoadedImage )
	{
		/* Optimize the image. */
		pOptimizedImage = SDL_DisplayFormat( pLoadedImage );

		/* Check the loaded image. */
		if( nullptr != pOptimizedImage )
		{
			/* Color key surface. */
			SDL_SetColorKey( pOptimizedImage, SDL_SRCCOLORKEY, SDL_MapRGB( pOptimizedImage->format, 0, 0xFF, 0xFF ) );
		}
	}
	
	/* Return the image. */
	return pOptimizedImage;
}

/* Surface applier. */
void apply_surface( int x, int y, SDL_Surface* pSource, SDL_Surface* pDestination, SDL_Rect *pClip = nullptr )
{
	/* Holds the offset. */
	SDL_Rect offset;

	/* Get offsets. */
	offset.x = x;
	offset.y = y;

	/* Blit the surface. */
	SDL_BlitSurface( pSource, pClip, pDestination, &offset ); 
}

/* Init SDL. */
bool init_SDL( void )
{
	/* Initialize all the subsystems. */
	if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 )
	{
		return false;
	}

	/* Set up the screen. */
	pScreen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE );

	/* Check the screen. */
	if( nullptr == pScreen )
	{
		return false;
	}

	/* Set the window caption. */
	SDL_WM_SetCaption( "Motion Tutorial", nullptr );

	return true;
}

/* File Loader. */
bool load_files( void )
{
	/* Load the dot image. */
	pDot = load_image( "dot.bmp" );

	/* Check the dot. */
	if( nullptr == pDot )
	{
		return false;
	}

	/* Everything went well, return true. */
	return true;
}

/* Clean up. */
void clean_SDL( void )
{
	/* Free the dot. */
	SDL_FreeSurface( pDot );

	/* QUIT SDL. */
	SDL_Quit();
}



/************* DOT CLASS ****************/
Dot::Dot()
{
	/* Initialize the offset. */
	m_iX = 0;
	m_iY = 0;

	/* Initialize the velocity. */
	m_iXVel = 0;
	m_iYVel = 0;
}

/* Input handler for the dot. */
void Dot::handle_inputs( void )
{
	/* If the key is pressed down. */
	if( SDL_KEYDOWN == event.type )
	{
		/* Adjust the velocity. */
		switch( event.key.keysym.sym )
		{
		case SDLK_KP8:
			{
				m_iYVel -= DOT_HEIGHT / 2;
				break;
			}
		case SDLK_KP2:
			{
				m_iYVel += DOT_HEIGHT / 2;
				break;
			}
		case SDLK_KP4:
			{
				m_iXVel -= DOT_WIDTH / 2;
				break;
			}
		case SDLK_KP6:
			{
				m_iXVel += DOT_WIDTH / 2;
				break;
			}
		}
	}

	/* Else if the key is released. */
	else if( SDL_KEYUP == event.type )
	{
		/* Adjust the velocity. */
		switch( event.key.keysym.sym )
		{
		case SDLK_KP8:
			{
				m_iYVel += DOT_HEIGHT / 2;
				break;
			}
		case SDLK_KP2:
			{
				m_iYVel -= DOT_HEIGHT / 2;
				break;
			}
		case SDLK_KP4:
			{
				m_iXVel += DOT_WIDTH / 2;
				break;
			}
		case SDLK_KP6:
			{
				m_iXVel -= DOT_WIDTH / 2;
				break;
			}
		}
	}
}

/* Move the dot. */
void Dot::move_dot( void )
{
	/* Move the dot left or right. */
	m_iX += m_iXVel;

	/* Check to see if the dot has gone too far either left or right. */
	if( ( m_iX < 0 ) || ( m_iX + DOT_WIDTH > SCREEN_WIDTH ) )
	{
		/* Move back. */
		m_iX -= m_iXVel;
	}

	/* Move the dot up or down. */
	m_iY += m_iYVel;

	/* Check to see if the dot has gone too far either up or down. */
	if( ( m_iY < 0 ) || ( m_iY + DOT_HEIGHT > SCREEN_HEIGHT ) )
	{
		/* Move back. */
		m_iY -= m_iYVel;
	}
}

/* Show the dot on the screen. */
void Dot::show_dot( void )
{
	apply_surface( m_iX, m_iY, pDot, pScreen );
}


/************* TIMER CLASS ****************/
Timer::Timer()
{
	/* Initialize the timer. */
	m_iStartTick = 0;
	m_iPauseTick = 0;

	m_bPaused = false;
	m_bStarted = false;
}

/* Start the timer. */
void Timer::start( void )
{
	/* Start the timer. */
	m_bStarted = true;

	/* Unpause the timer. */
	m_bPaused = false;

	/* Get the current clock time. */
	m_iStartTick = SDL_GetTicks();
}

/* Stop the timer. */
void Timer::stop( void )
{
	/* Stop the timer. */
	m_bStarted = false;

	/* Unpause the timer. */
	m_bStarted = false;
}

/* Pause the timer. */
void Timer::pause( void )
{
	/* If the timer is running and isn't already paused. */
	if( ( m_bStarted == true ) && ( m_bPaused == false ) )
	{
		/* Pause the timer. */
		m_bPaused = true;

		/* Calculate the pause tick. */
		m_iPauseTick = SDL_GetTicks() - m_iStartTick;
	}
}

/* Unpause the timer. */
void Timer::unpause( void )
{
	/* If the timer is paused. */
	if( m_bPaused == true )
	{
		/* Unpause the timer. */
		m_bPaused = false;

		/* Reset the starting tick. */
		m_iStartTick = SDL_GetTicks() - m_iPauseTick;

		/* Reset the pause tick. */
		m_iPauseTick = 0;
	}
}

/* Get the ticks. */
int Timer::get_ticks( void )
{
	/* Check if the timer is running. */
	if( m_bStarted == true )
	{
		/* If the timer is paused. */
		if( m_bPaused == true )
		{
			/* Return the tick when the timer was paused. */
			return m_iPauseTick;
		}
		else
		{
			/* Return the current time - start time. */
			return SDL_GetTicks() - m_iStartTick;
		}
	}

	/* If the timer is not running. */
	return 0;
}

bool Timer::has_started( void )
{
	return m_bStarted;
}

bool Timer::is_paused( void )
{
	return m_bPaused;
}




/*********** MAIN **************/
int main( int argc, char** argv )
{
	/* QUIT flag. */
	bool bQUit = false;

	/* The dot we will be using. */
	Dot myDot;

	/* FPS regulator. */
	Timer fps;

	/* Intialize. */
	if( false == init_SDL() )
	{
		return 1;
	}

	/* Load the files. */
	if( false == load_files() )
	{
		return 1;
	}

	/* While the user hasn't quit. */
	while( false == bQUit )
	{
		/* Start the timer. */
		fps.start();

		/* Event handler. */
		while( SDL_PollEvent( &event ) )
		{
			/* Handle the events for the dot. */
			myDot.handle_inputs();

			/* Check to see if the user has Xed out the window. */
			if( SDL_QUIT == event.type )
			{
				/* QUIT the program. */
				bQUit = true;
			}
		}

		/* Move the dot. */
		myDot.move_dot();

		/* Fill the screen white. */
		SDL_FillRect( pScreen, &pScreen->clip_rect, SDL_MapRGB(pScreen->format, 0xFF, 0xFF, 0xFF) );

		/* Show the dot on the screen. */
		myDot.show_dot();

		/* Update the screen. */
		if( -1 == SDL_Flip( pScreen ) )
		{
			return 1;
		}

		/* Cap the frame rate. */
		if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )
		{
			SDL_Delay( (1000 / FRAMES_PER_SECOND) - fps.get_ticks() );
		}
	}

	/* Clean up. */
	clean_SDL();

	return 0;
}